package io.github.consoles.distribution;

import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import java.util.zip.CRC32;

/**
 * Created by yiihua-013 on 16/10/22.
 * ref http://consoles.github.io/2016/10/18/memcached/
 * <p>
 * 一致性hash
 */

public class ConsistentHash implements Hash, Distribution {

    //    private Map<Long, String> nodes    = new TreeMap<>();
    private Map<Long, String> position = new TreeMap<>();

    private static final int MUL = 64; // 每个物理节点有64个虚拟节点

    public String lookup(String key) {
        long point = this.hash(key); // 算出key的落点
        // 找出落点所在的区间
        String node = (String) position.values().toArray()[0];
        for (Map.Entry<Long, String> entry : position.entrySet()) {
            if (point <= entry.getKey()) {
                node = entry.getValue();
                break;
            }
        }
        return node;
    }

    public long hash(String str) {
        CRC32 crc32 = new CRC32();
        crc32.update(str.getBytes());
        long ret = crc32.getValue();
        return ret > 0 ? ret : -ret;
    }

    /**
     * 加入1个物理节点相当于加入了64个虚拟节点
     *
     * @param node,例如:192.168.1.1
     */
    public void addNode(String node) {
        for (int i = 0; i < MUL; i++) {
            String vNodeName = "v_node_" + node + "_" + i;
            this.position.put(hash(vNodeName), node); // 每64个虚拟节点对应一个物理节点
        }
//        long pos = this.hash(node); // 根据节点名计算出在圆环上的位置
//        this.nodes.put(pos, node); // 13亿=>"192.168.1.1"则表示13亿这个位置上有一个真实节点为192.168.1.1
    }

    /**
     * 删除一个物理节点需要删除64个虚拟节点
     */
    public void deleteNode(String node) {
        Iterator<Map.Entry<Long, String>> iterator = position.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<Long, String> entry = iterator.next();
            if (entry.getValue().equals(node)) {
//                position.remove(entry.getKey());
                iterator.remove();
            }
        }
        // 以下代码将抛出并发修改异常
//        for (Map.Entry<Long,String> entry:position.entrySet()){
//            if (entry.getValue().equals(node)) {
//                position.remove(entry.getKey());
//            }
//        }
    }

//    public void printNodes() {
//        for (Map.Entry<Long, String> entry : nodes.entrySet()) {
//            System.out.println(entry.getKey() + "=>" + entry.getValue());
//        }
//    }
}
